package com.xlabs.server;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.xlabs.server.DO.PMF;
import com.xlabs.server.DO.QuestionDO;
import com.xlabs.server.DO.QuestionQueryResult;
import com.xlabs.server.DO.ReplyDO;
import com.xlabs.server.DO.VideoDO;
import com.xlabs.client.DTO.QuestionDTO;
import com.xlabs.client.DTO.ReplyDTO;
import com.xlabs.client.DTO.TransferDTO;
import com.xlabs.client.CommentingService;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import java.util.*;


/**
 * TimedCommentServiceImpl class.
 */
public class CommentingServiceImpl extends RemoteServiceServlet implements
        CommentingService {

    /**
	 * 
	 */
	private static final long serialVersionUID = 1714415619178522139L;

	/**
     * Posts comment for a video.
     *
     * @param comment        comment text.
     * @param time           time of comment.
     * @param currentVideoId video id.
     */
    public void postQuestion(String comment, int time, String currentVideoId) 
    {
        VideoDO.addQuestionToVideo(comment, time, currentVideoId);
    }
    
	/**
	 * Gets questions asked in a given time interval.
	 * @param videoId video id.
	 * @param startTimeIndex start time 
	 * @param endTimeIndex end time.
	 * @param direction specifies the direction of query.  
	 * @param pageSize how many questions to return. 
	 * @param asyncCallBack callback method.
	 * @return transfer object including the questions.
	 */
    public TransferDTO getPagedQuestionsWithStartTimeInInterval(String videoId, String startTimeIndex, String endTimeIndex, int direction, int pageSize)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		
    	VideoDO.AddVideo(videoId);

    	try
		{
			QuestionQueryResult queryResult = QuestionDO.queryQuestionPageWithStartTimeInInterval(startTimeIndex, endTimeIndex, direction, pageSize + 1, pm);
			boolean fullPage = (queryResult.getQuestions().size() == pageSize + 1);
			List<QuestionDTO> dtoList = QuestionDO.toDTOList(queryResult.getQuestions());
			TransferDTO response = new TransferDTO();

			if (direction == TransferDTO.LEFT_DIRECTION)
			{
				Collections.reverse(dtoList);
				response.addKeyValue(TransferDTO.HAS_PREV_QUESTIONPAGE_KEY, fullPage);
				response.addKeyValue(TransferDTO.NEXT_PAGE_QUESTION_START_TIME_INDEX_KEY, endTimeIndex);

				if (fullPage)
				{
					dtoList.remove(0);
				}
			}
			else if (direction == TransferDTO.RIGHT_DIRECTION)
			{
				response.addKeyValue(TransferDTO.HAS_NEXT_QUESTIONPAGE_KEY, fullPage);
				response.addKeyValue(TransferDTO.NEXT_PAGE_QUESTION_START_TIME_INDEX_KEY, fullPage ? queryResult.getQuestions().get(pageSize).getStartTimeIndex() : null);
				
				if (fullPage)
				{
					dtoList.remove(pageSize);
				}
			}

			response.addKeyValue(TransferDTO.QUESTIONLIST_KEY, dtoList);

			return response;
		} 
		finally
		{
			pm.close();
		}

	}
    
    /**
     * see {@link CommentingService}{@link #getPagedQuestionsAroundTime(String, int, int)}
     */
    public TransferDTO getPagedQuestionsWithStartTimeAroundTime(String videoId, int time, int pageSize)
	{
    	PersistenceManager pm = PMF.get().getPersistenceManager();
    	
    	try
    	{
	    	VideoDO.AddVideo(videoId);
	    	
			List<QuestionDO> rightQuestions = QuestionDO.queryQuestionPageWithStartTimeInInterval(
												String.valueOf(time), 
												String.valueOf(Integer.MAX_VALUE), 
												TransferDTO.RIGHT_DIRECTION,
												pageSize + 1, pm).getQuestions();

			List<QuestionDO> leftQuestions = QuestionDO.queryQuestionPageWithStartTimeInInterval(
												String.valueOf(Integer.MIN_VALUE), 
												String.valueOf(time), 
												TransferDTO.LEFT_DIRECTION,
												pageSize + 1, pm).getQuestions();

			TransferDTO response = new TransferDTO();
			List<QuestionDTO> dtoList = new ArrayList<QuestionDTO>();
			Boolean hasNext = null;
			Boolean hasPrev = null;
			
			if (rightQuestions.size() >= pageSize)
			{
				dtoList = QuestionDO.toDTOList(rightQuestions.subList(0, Math.min(rightQuestions.size(), pageSize)));
				hasNext = (rightQuestions.size() == pageSize + 1);
				hasPrev = (leftQuestions.size() >= 1);
			}
			else
			{
				List<QuestionDO> mergedQuestions = new ArrayList<QuestionDO>();
				int questionsToAdd = pageSize - rightQuestions.size();
				mergedQuestions = leftQuestions.subList(0, Math.min(questionsToAdd,	leftQuestions.size()));
				int questionsAddedFromLeft = mergedQuestions.size();
	
				Collections.reverse(mergedQuestions);
	
				List<QuestionDO> questionsToAddFromRight = rightQuestions.subList(
																			0, 
																			Math.min(
																					rightQuestions.size(), 
																					pageSize - mergedQuestions.size()
																					)
																				 );
	
				mergedQuestions.addAll(questionsToAddFromRight);
	
				dtoList = QuestionDO.toDTOList(mergedQuestions);
				hasNext = false;
				hasPrev = (leftQuestions.size() > questionsAddedFromLeft); 
			}
			
	    	response.addKeyValue(TransferDTO.QUESTIONLIST_KEY, dtoList);
	    	response.addKeyValue(TransferDTO.HAS_NEXT_QUESTIONPAGE_KEY, hasNext);
	    	response.addKeyValue(TransferDTO.HAS_PREV_QUESTIONPAGE_KEY, hasPrev);
			response.addKeyValue(TransferDTO.NEXT_PAGE_QUESTION_START_TIME_INDEX_KEY, hasNext ? rightQuestions.get(pageSize).getStartTimeIndex() : null);
	    	
	    	return response;
    	}
    	finally
    	{
    		pm.close();
    	}
	}

	/**
     * Gets all comments for the given video that were posted interval seconds from the given time. 
     * @param videoId video id.
     * @param time the time for which the questions will be extracted. 
     * @param interval how far should I go from the time point provided.
     * @param async callback.
     */
    public QuestionDTO[] getQuestionsInSymmetricTimeInterval(String videoId, int time, int interval)
    {
        PersistenceManager pm = PMF.get().getPersistenceManager();
        QuestionDTO[] result;

        try {

            VideoDO.AddVideo(videoId);

            result = QuestionDO.getQuestionsInSymmetricTimeInterval(time, interval);

        }
        finally {

            pm.close();
        }

        return result;
    }

    /**
     * Posts a reply for a question.
     *
     * @param reply             reply.
     * @param questionStringKey question key.
     */
    public void postReply(String reply, String questionStringKey) {
        PersistenceManager pm = PMF.get().getPersistenceManager();

        try {
            Key questionKey = KeyFactory.stringToKey(questionStringKey);
            QuestionDO questionDO = pm.getObjectById(QuestionDO.class, questionKey);

            ReplyDO replyDO = new ReplyDO(reply, new Date());

            replyDO.setReplyTo(questionKey);
            replyDO.setOriginalQuestion(questionKey);

            UserService userService = UserServiceFactory.getUserService();
            replyDO.setCreatedBy(userService.getCurrentUser());

            pm.makePersistent(replyDO);

            questionDO.addReply(replyDO.getKey());
        }
        finally 
        {
            pm.close();
        }
    }

    /**
     * Posts an inner reply for another reply in conversation.
     *
     * @param reply          reply.
     * @param parentReplyEncodedKey question key.
     * @param questionEncodedKey corresponding question's encoded key
     */
    public void posInnertReply(String reply, String parentReplyEncodedKey, String questionEncodedKey) {

        Key parentReplyKey = KeyFactory.stringToKey(parentReplyEncodedKey);
        Key questionKey = KeyFactory.stringToKey(questionEncodedKey);

        PersistenceManager pm = PMF.get().getPersistenceManager();

        try {
            ReplyDO parentReplyDO = pm.getObjectById(ReplyDO.class, parentReplyKey);

            ReplyDO replyDO = new ReplyDO(reply, new Date());

            replyDO.setReplyTo(parentReplyKey);
            replyDO.setOriginalQuestion(questionKey);

            UserService userService = UserServiceFactory.getUserService();
            replyDO.setCreatedBy(userService.getCurrentUser());

            pm.makePersistent(replyDO);

            parentReplyDO.addReply(replyDO.getKey());
        }
        finally {
            pm.close();
        }
    }

    /**
     * Gets the conversation related to the question given.
     *
     * @param questionEncodedStringKey question's key.
     * @return conversation.
     */
    @SuppressWarnings("unchecked")
	public List<ReplyDTO> getConversation(String questionEncodedStringKey) {
        PersistenceManager pm = PMF.get().getPersistenceManager();

        Key questionKey = KeyFactory.stringToKey(questionEncodedStringKey);

        List<ReplyDO> orderedReplies = new ArrayList<ReplyDO>();

        try {
            Query query = pm.newQuery(ReplyDO.class);
            query.setFilter("originalQuestion == questionKey");
            query.setOrdering("createdOn asc");
            query.declareParameters("String questionKey");

            try {
                orderedReplies = (List<ReplyDO>) query.execute(questionKey);
            }
            finally {
                query.closeAll();
            }

            return this.createConversation(orderedReplies);
        }
        finally {
            pm.close();
        }
    }

    /**
     * Orders the replies and return them.
     *
     * @param orderedReplies List of unordered replies.
     * @return Ordered replies.
     */
    private List<ReplyDTO> createConversation(List<ReplyDO> orderedReplies) {
        List<ReplyDTO> result = new ArrayList<ReplyDTO>(orderedReplies.size());

        Map<Key, ReplyDO> repliesKeyToReplyDOMap = new HashMap<Key, ReplyDO>(orderedReplies.size());
        Map<Key, ReplyDTO> repliesKeyToReplyDTOMap = new HashMap<Key, ReplyDTO>(orderedReplies.size());
        
        for (ReplyDO reply : orderedReplies) {
            repliesKeyToReplyDOMap.put(reply.getKey(), reply);
        }

        for (ReplyDO reply : orderedReplies)
        {
            ReplyDTO replyDTO = reply.toReplyDTO();
            result.add(replyDTO);
            repliesKeyToReplyDTOMap.put(reply.getKey(), replyDTO);
            
            // building parent-child relationship
            Key replyToKey = reply.getReplyTo();
            ReplyDTO parentReplyDTO = repliesKeyToReplyDTOMap.get(replyToKey);
            replyDTO.setParent(parentReplyDTO);
            if (parentReplyDTO != null)
            {
            	parentReplyDTO.addReply(replyDTO);
            }
        }

        return result;
    }
}